/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sirtet.tetris;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

/**
 * This is a derived class of TetrisBoard that supports battling function,
 * i.e. send eliminated lines to its opponent
 * @author guangwei.zhu
 */
public class BattlingTetrisBoard extends TetrisBoard implements BattlingTetrisTarget {

    /**
     * Constant prefix indicating an attacked line (used in networking)
     */
    public static final String MSG_ATTACK = "ATTACK ";

    protected LinkedList<Object[]> attackQueue = new LinkedList<Object[]>();
    private Set<BattlingTetrisTarget> targets = new HashSet<BattlingTetrisTarget>();
    private Set<BoardAttackListener> attackListeners = new HashSet<BoardAttackListener>();
    private Object[][] snapshot;

    public BattlingTetrisBoard(int width, int height) {
        super(width, height);

    }

    @Override
    protected synchronized boolean beforeNextPiece() {
        dumpAttackBlocks();   // Force to dump the attack buffer

        return true;
    }

    @Override
    protected synchronized boolean beforeFixingPiece() {
        // Create a snapshot
        snapshot = this.createSnapshot();

        return true;
    }

    /**
     * Register an attack target
     * @param target an attack target to which eliminated blocks will be sent
     */
    public void addAttackTarget(BattlingTetrisTarget target) {
        targets.add(target);
    }

    /**
     * Register a collection of attack targets
     * @param targets attack targets to which eliminated blocks will be sent
     */
    public void addAttackTargets(Collection<BattlingTetrisTarget> targets) {
        this.targets.addAll(targets);
    }

    /**
     * Remove all attack targets
     */
    public void clearAttackTarget() {
        targets.clear();
    }

    /**
     * Unregister an attack target
     * @param target an attack target to which eliminated blocks will be sent
     */
    public void removeAttackTarget(BattlingTetrisTarget target) {
        targets.remove(target);
    }

    /**
     * Send eliminated blocks to targets
     * @param attackBlocks eliminated blocks
     */
    public void attackTargets(Object[][] attackBlocks) {
        for (BattlingTetrisTarget target : this.targets) {
            target.takeAttack(attackBlocks);
        }
    }

    /**
     * Add a BoardAttackListener to the listener list
     * @param listener a BoardAttackListener to be added
     */
    public void addBoardAttackListener(BoardAttackListener listener) {
        attackListeners.add(listener);
    }

    /**
     * Remove a BoardAttackListener from the listener list
     * @param listener a BoardAttackListener to be removed
     */
    public void removeBoardAttackListener(BoardAttackListener listener) {
        attackListeners.remove(listener);
    }

    /**
     * Notify all registered BoardListener about elimination event.  Overriden
     * to hack the process and send the eliminated lines to targets.
     */
    @Override
    @SuppressWarnings({"unchecked"})
    protected void notifyBlocksEliminated(Object eliminatedInfo) {
        Object[] lineNumbers = (Object[]) eliminatedInfo;
        ArrayList<Object[]> attackLines = new ArrayList<Object[]>();
        boolean fragileBlock;

        for (Object i : lineNumbers) {
            fragileBlock = false;
            for (Object j : snapshot[(Integer) i]) {
                if ((Integer) j == TetrisPiece.FRAGILE) {
                    fragileBlock = true;
                    break;
                }
            }
            if (!fragileBlock) {    // The line is normal, i.e. transferrable
                attackLines.add(snapshot[(Integer) i]);
            }
        }

        Object[][] attackArray = new Object[attackLines.size()][];
        int j = 0;

        for (Object[] obj : attackLines) {
            attackArray[j++] = obj;
        }

        // Send the blocks to the target
        attackTargets(attackArray);

        super.notifyBlocksEliminated(eliminatedInfo);      // Continue to notify listeners
    }

    /**
     * Take the attack blocks sent by an attacker board
     * @param attackBlocks a 2D array containing pieces to be appended
     */
    public synchronized void takeAttack(Object[][] attackBlocks) {
        int i;

        notifyAttacked(attackBlocks);

        for (Object[] line : attackBlocks) {
            for (i = 0; i < line.length; i++) {
                if ((Integer) line[i] != TetrisPiece.EMPTY) {
                    line[i] = TetrisPiece.FRAGILE;      // Attack blocks are "fragile"
                }
            }
            attackQueue.addLast(line);
        }
    }

    protected void notifyAttacked(Object[][] attackData) {
        for (BoardAttackListener listener : attackListeners) {
            listener.BoardAttacked(new BoardAttackedEvent(this, attackData));
        }
    }

    /**
     * Reset the game board and clear all data, including attack buffer
     */
    @Override
    public synchronized void reset() {
        attackQueue.clear();
        super.reset();
    }

    /**
     * Put everything that is in attack buffer at the bottom of the board.
     */
    protected synchronized void dumpAttackBlocks() {
        int count = attackQueue.size();
        int i;

        synchronized (this) {
            // If there are sooo many lines in buffer...
            while (count >= this.boardHeight) {
                attackQueue.pollFirst();    // Get rid of some
                count = attackQueue.size();
            }

            // Shift up existing blocks
            for (i = 0; i < this.boardHeight - count; i++) {
                blocks[i] = blocks[i + count];
            }

            // Stuff attacked blocks at the bottom
            for (i = this.boardHeight - count; i < this.boardHeight; i++) {
                blocks[i] = attackQueue.pollFirst();
            }
        }
        // Notify that the contents are changed
        notifyChanged();
    }
}
